package cn.virens.mybatis.mapper.example.mapper;

import java.util.stream.Collectors;

import org.apache.ibatis.builder.annotation.ProviderContext;

import cn.virens.common.exception.APIException;
import cn.virens.common.utils.StreamUtil;
import cn.virens.mybatis.mapper.example.util.SQLBuidler;
import io.mybatis.provider.EntityTable;
import io.mybatis.provider.SqlScript;

public class ExampleProvider {
	private static final String TRIM = "AND |OR ";

	/**
	 * 根据 Example 删除
	 *
	 * @param  providerContext 上下文
	 * @return                 cacheKey
	 */
	public static String deleteByExample(ProviderContext providerContext) {
		return SqlScript.caching(providerContext, (entity, util) -> {
			SQLBuidler buidler = SQLBuidler.of("DELETE FROM ", entity.table());

			buidler.assertNotNull("example.criteriaList");

			return buildCriteria(entity, buidler).toString();
		});
	}

	/**
	 * 根据 Example 条件批量更新实体信息
	 *
	 * @param  providerContext 上下文
	 * @return                 cacheKey
	 */
	public static String updateByExample(ProviderContext providerContext) {
		return SqlScript.caching(providerContext, (entity, util) -> {
			SQLBuidler buidler = SQLBuidler.of("UPDATE ", entity.table());

			buidler.assertNotNull("example.criteriaList");

			buildUpdateColumns(entity, buidler);
			buildCriteria(entity, buidler);

			return buidler.toString();
		});
	}

	/**
	 * 根据 Example 条件批量更新实体不为空的字段
	 *
	 * @param  providerContext 上下文
	 * @return                 cacheKey
	 */
	public static String updateByExampleSelective(ProviderContext providerContext) {
		return SqlScript.caching(providerContext, (entity, util) -> {
			SQLBuidler buidler = SQLBuidler.of("UPDATE ", entity.table());

			buidler.assertNotNull("example.criteriaList");

			buildUpdateSelective(entity, buidler);
			buildCriteria(entity, buidler);

			return buidler.toString();
		});
	}

	/**
	 * 根据 Example 条件批量查询，根据 Example 条件查询总数，查询结果的数量由方法定义
	 *
	 * @param  providerContext 上下文
	 * @return                 cacheKey
	 */
	public static String selectByExample(ProviderContext providerContext) {
		return SqlScript.caching(providerContext, (entity, util) -> {
			SQLBuidler buidler = SQLBuidler.of("SELECT ");

			buildSelectColumns0(entity, buidler);
			buildSelectColumns1(entity, buidler);

			buidler.appendLE("FROM ", entity.table());

			buildJoinTable(entity, buidler);
			buildCriteria(entity, buidler);

			buildGroupBy(entity, buidler);
			buildGaving(entity, buidler);
			buildOrder(entity, buidler);
			buildLimit(entity, buidler);

			return buidler.toString();
		});
	}

	/**
	 * 根据 Example 条件批量查询，根据 Example 条件查询总数，查询结果的数量由方法定义
	 *
	 * @param  providerContext 上下文
	 * @return                 cacheKey
	 */
	public static String selectCountByExample(ProviderContext providerContext) {
		return SqlScript.caching(providerContext, (entity, util) -> {
			SQLBuidler buidler = SQLBuidler.of("SELECT COUNT(1) ");
			buidler.appendLE("FROM ", entity.table());

			buildJoinTable(entity, buidler);
			buildCriteria(entity, buidler);

			buildGroupBy(entity, buidler);
			buildGaving(entity, buidler);
			buildOrder(entity, buidler);
			buildLimit(entity, buidler);

			return buidler.toString();
		});
	}

	private static void buildSelectColumns0(EntityTable entity, SQLBuidler buidler) throws APIException {
		buidler.ifNotEmpty("example.selectColumns", a -> a.foreach("example.selectColumns", "c", ",", c -> c.append("${c}")));
	}

	private static void buildSelectColumns1(EntityTable entity, SQLBuidler buidler) throws APIException {
		buidler.ifEmpty("example.selectColumns", a -> a.append(entity.selectColumns().stream().map(z -> {
			return SQLBuidler.of(entity.table(), ".", z.column()).toString();
		}).collect(Collectors.joining(","))));

	}

	private static SQLBuidler buildLimit(EntityTable entity, SQLBuidler buidler) throws APIException {
		return buidler.ifNotEmpty("example.limitByClause", a -> a.append("LIMIT ${example.limitByClause}"));
	}

	private static SQLBuidler buildOrder(EntityTable entity, SQLBuidler buidler) throws APIException {
		return buidler.ifNotEmpty("example.orderByClause", a -> a.append("ORDER BY ${example.orderByClause}"));
	}

	private static SQLBuidler buildCriteria(EntityTable entity, SQLBuidler buidler) throws APIException {
		return buidler.trim("WHERE", TRIM, a -> a.foreach("example.criteriaList", "citem", b -> {
			b.trim("${citem.andOr}\n", "(", ")", TRIM, "", c -> condition(c, "citem.criterionList"));
		}));
	}

	private static SQLBuidler buildGaving(EntityTable entity, SQLBuidler buidler) throws APIException {
		return buidler.trim("HAVING ", TRIM, a -> condition(a, "example.havingList"));
	}

	private static SQLBuidler buildGroupBy(EntityTable entity, SQLBuidler buidler) throws APIException {
		return buidler.ifNotEmpty("example.groupByClause", a -> a.append("GROUP BY ${example.groupByClause}"));
	}

	private static SQLBuidler buildJoinTable(EntityTable entity, SQLBuidler buidler) throws APIException {
		return buidler.foreach("example.joinList", "joinTable", a -> {
			a.trim("${joinTable.type} JOIN ${joinTable.table} \n", "ON ", TRIM, b -> {
				condition(b, "joinTable.criterionList");
			});
		});
	}

	private static void buildUpdateColumns(EntityTable entity, SQLBuidler buidler) throws APIException {
		buidler.set(",", () -> StreamUtil.stream(entity.updateColumns()).map(z -> {
			return z.columnEqualsProperty("entity.");
		}));
	}

	private static void buildUpdateSelective(EntityTable entity, SQLBuidler buidler) throws APIException {
		buidler.set("", () -> StreamUtil.stream(entity.updateColumns()).map(z -> {
			return SQLBuidler.of().ifTest(z.notNullTest("entity."), a -> {
				a.append(z.columnEqualsProperty("entity."), ",");
			});
		}).map(SQLBuidler::toString));
	}

	private static SQLBuidler condition(SQLBuidler buidler, String criterionList) throws APIException {
		return buidler.foreach(criterionList, "item", a -> a.choose((b) -> {
			b.chooseWhen("item.noValue", c -> c.append("\n${item.andOr} ${item.condition}"));
			b.chooseWhen("item.singleValue", c -> c.append("\n${item.andOr} ${item.condition} #{item.value1}"));
			b.chooseWhen("item.betweenValue", c -> c.append("\n${item.andOr} ${item.condition} #{item.value1} and #{item.value2}"));
			b.chooseWhen("item.listValue", c -> c.append("\n${item.andOr} ${item.condition} ").foreach("item.value1", "v", ",", "(", ")", d -> d.append("#{v}")));
		}));
	}

}
